package com.huangyi.fileservice.service.impl;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.huangyi.commondomain.domain.ResultCode;
import com.huangyi.commondomain.exception.ServiceException;
import com.huangyi.fileservice.config.OSSProperties;
import com.huangyi.fileservice.constants.OSSConstants;
import com.huangyi.fileservice.domain.DTO.FileDTO;
import com.huangyi.fileservice.domain.DTO.SignDTO;
import com.huangyi.fileservice.service.IFileService;
import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.InputStream;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
 * 阿里云 OSS 文件服务实现类
 */
@Slf4j
@Service
@ConditionalOnProperty(value = "storage.type", havingValue = "oss")
public class OSSFileServiceImpl implements IFileService {

    @Autowired
    private OSSClient ossClient;
    @Autowired
    private OSSProperties ossProperties;

    @Override
    /**
     * 上传文件到阿里云 OSS
     *
     * @param file 待上传的文件
     * @return {@link FileDTO} 上传后的文件信息，包括文件名、路径和目录
     * @throws ServiceException 当上传失败或 OSS 返回异常时抛出
     */
    public FileDTO uploadFile(MultipartFile file) {
        try {
            // 获取文件输入流
            InputStream inputStream = file.getInputStream();

            // 获取原始文件名
            String originalFilename = file.getOriginalFilename();
            String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);

            // 在 OSS 中存储的文件名 = 文件目录 + UUID + 文件后缀名
            String objectName = ossProperties.getPathPrefix() + UUID.randomUUID() + "." + extName;

            // 设置文件元信息（公开读权限）
            ObjectMetadata objectMetadata = new ObjectMetadata();
            objectMetadata.setObjectAcl(CannedAccessControlList.PublicRead);

            // 创建 PutObjectRequest 对象
            PutObjectRequest putObjectRequest = new PutObjectRequest(
                    ossProperties.getBucketName(),
                    objectName,
                    inputStream,
                    objectMetadata
            );

            // 上传文件到 OSS
            PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);

            if (putObjectResult == null || StringUtils.isBlank(putObjectResult.getRequestId())) {
                log.error("上传 OSS 异常，putObjectResult 未正常返回: {}", putObjectRequest);
                throw new ServiceException(ResultCode.OSS_UPLOAD_FAILED);
            }

            // 封装返回结果
            FileDTO sysFileDTO = new FileDTO();
            sysFileDTO.setUrl(ossProperties.getBaseUrl() + objectName);
            sysFileDTO.setKey(objectName);
            sysFileDTO.setName(new File(objectName).getName());

            return sysFileDTO;
        } catch (Exception e) {
            log.error("上传 OSS 异常", e);
            throw new ServiceException(ResultCode.OSS_UPLOAD_FAILED);
        }
    }

    /**
     * 获取阿里云 OSS 文件直传签名信息
     *
     * @return {@link SignDTO} 包含签名信息、凭证、请求时间、策略、上传地址和路径前缀
     * @throws ServiceException 当签名生成失败时抛出
     */
    @Override
    public SignDTO getSign() {
        try {
            // 获取 ak / sk
            String accesskeyid = ossProperties.getAccessKeyId();
            String accesskeysecret = ossProperties.getAccessKeySecret();

            // 获取当前时间
            Instant now = Instant.now();

            // 构建返回数据
            SignDTO signDTO = new SignDTO();
            signDTO.setHost(ossProperties.getBaseUrl());
            signDTO.setPathPrefix(ossProperties.getPathPrefix());

            // 步骤1：创建 policy
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> policy = new HashMap<>();

            // expiration = formatter.format() 过期时间（UTC）
            DateTimeFormatter formatter = DateTimeFormatter
                    .ofPattern(OSSConstants.SIGN_EXPIRE_TIME_FORMAT)
                    .withZone(java.time.ZoneOffset.UTC);
            String expiration = formatter.format(now.plusSeconds(ossProperties.getExpre()));
            policy.put("expiration", expiration);//expiration是ISO 8601 格式(UTC 时间)，阿里云 OSS 直传策略要求过期时间必须是这种格式

            // 组装 policy 条件
            List<Object> conditions = new ArrayList<>();

            // bucket 条件
            Map<String, String> bucketCondition = new HashMap<>();
            bucketCondition.put("bucket", ossProperties.getBucketName());
            conditions.add(bucketCondition);

            // 签名版本
            Map<String, String> signatureVersionCondition = new HashMap<>();
            signatureVersionCondition.put("x-oss-signature-version", "OSS4-HMAC-SHA256");
            conditions.add(signatureVersionCondition);

            // dateStr = formatter.format() 身份凭证/Credential 条件,签名的有效性只在当天有效
            Map<String, String> credentialCondition = new HashMap<>();
            formatter = DateTimeFormatter.ofPattern(OSSConstants.SIGN_DATE_FORMAT)
                    .withZone(java.time.ZoneOffset.UTC);//formatter 保存“时间长什么样”的规则。通过 format 方法输出固定格式的时间
            String dateStr = formatter.format(now);
            String xOSSCredential = accesskeyid + "/" + dateStr + "/" +
                    ossProperties.getRegion() + "/oss/aliyun_v4_request";
            signDTO.setXOSSCredential(xOSSCredential);
            credentialCondition.put("x-oss-credential", xOSSCredential);
            conditions.add(credentialCondition);

            // xOSSDate = formatter.format() 区别于 expiration，xOSSDate 也是一种时间限制
            // expiration 像是“会议结束时间”，到了就所有人都得走。
            // x-oss-date 像是“门禁刷卡时间限制”，你必须在刷卡后几分钟内进门。
            Map<String, String> dateCondition = new HashMap<>();
            formatter = DateTimeFormatter.ofPattern(OSSConstants.SIGN_REQUEST_TIME_FORMAT)
                    .withZone(java.time.ZoneOffset.UTC);
            String xOSSDate = formatter.format(now);
            signDTO.setXOSSDate(xOSSDate);
            dateCondition.put("x-oss-date", xOSSDate);
            conditions.add(dateCondition);

            // 文件大小限制
            conditions.add(Arrays.asList("content-length-range", ossProperties.getMinLen(), ossProperties.getMaxLen()));

            // 上传成功返回码
            conditions.add(Arrays.asList("eq", "$success_action_status", "200"));

            // 将条件放入 policy
            policy.put("conditions", conditions);

            // policy 转 JSON
            String jsonPolicy = mapper.writeValueAsString(policy);

            // 步骤2：构造待签名字符串（Base64编码的policy）
            String policyBase64 = new String(Base64.encodeBase64(jsonPolicy.getBytes()));
            signDTO.setPolicy(policyBase64);

            // 步骤3：计算 SigningKey
            byte[] dateKey = hmacsha256(("aliyun_v4" + accesskeysecret).getBytes(), dateStr);
            byte[] dateRegionKey = hmacsha256(dateKey, ossProperties.getRegion());
            byte[] dateRegionServiceKey = hmacsha256(dateRegionKey, "oss");
            byte[] signingKey = hmacsha256(dateRegionServiceKey, "aliyun_v4_request");

            // 步骤4：计算 Signature
            byte[] result = hmacsha256(signingKey, policyBase64);
            String signature = BinaryUtil.toHex(result);
            signDTO.setSignature(signature);

            return signDTO;
        } catch (Exception e) {
            log.error("生成直传签名失败", e);
            throw new ServiceException(ResultCode.PRE_SIGN_URL_FAILED);
        }
    }

    /**
     * HMAC-SHA256 计算方法
     */
    public static byte[] hmacsha256(byte[] key, String data) {
        try {
            // 初始化 HMAC 密钥规格
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA256");

            // 获取 Mac 实例
            Mac mac = Mac.getInstance("HmacSHA256");

            // 使用密钥初始化
            mac.init(secretKeySpec);

            // 执行 HMAC 计算
            return mac.doFinal(data.getBytes());
        } catch (Exception e) {
            log.error("生成直传签名失败", e);
            throw new ServiceException(ResultCode.PRE_SIGN_URL_FAILED);
        }
    }



}
